package com.project.java_doc_searcher.searcher;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

public class Parser {

    private static final String INPUT_PATH = "D:/CODE/doc_searcher_index/jdk-8u411-docs-all/api";

    // 创建一个 index 实例
    private  Index index = new Index();

    private AtomicLong t1 = new AtomicLong(0);
    private AtomicLong t2 = new AtomicLong(0);

    // 单线程制作索引
    public void run(){
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始！");
        // 整个 searcher.Parser 类的入口
        // 1. 根据 INPUT_PATH 路径，枚举出该路径中所有的 html 文件，而这一步的实现需要把所有字目录中的文件获取到
        ArrayList<File> fileList = new ArrayList<>();
        enumFile(INPUT_PATH, fileList);

        long endEnumFile = System.currentTimeMillis();
        System.out.println("枚举文件完毕！消耗时间：" + (endEnumFile - beg));
//        System.out.println(fileList);
//        System.out.println("总文件数： " + fileList.size());
        // 2. 针对上面罗列出的文件的路径，依次打开文件并读取内容，然后进行解析和构建索引
        for (File f : fileList){
            // 解析文件内容，得到文件标题、文件 URL 、文件正文
            System.out.println("开始解析" + f.getAbsolutePath());
            parseHTML(f);
        }

        long endFor = System.currentTimeMillis();
            System.out.println("遍历文件完毕！消耗时间：" + (endFor - endEnumFile) + "ms");

        // 3.把在内存中构造好的索引数据结构，保存到指定的文件中
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("索引制作结束！消耗时间：" + (end - beg) + "ms");
    }

    // 多线程制作索引
    public void runByThread() throws InterruptedException {
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始！");

        // 1、枚举出所有文件
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH, files);

        // 2、循环遍历文件，同时为了能够通过多线程制作索引，这里直接引入线程池
        CountDownLatch latch = new CountDownLatch(files.size());
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        for (File f : files){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("解析" + f.getAbsolutePath());
                    parseHTML(f);
                    latch.countDown();
                }
            });
        }

        // await 方法会阻塞，直到所有线程都执行完毕，才阻塞结束
        latch.await();
        // 手动把线程池里的线程都关掉
        executorService.shutdown();
        // 3、保存索引
        index.save();

        long end = System.currentTimeMillis();
        System.out.println("索引文件制作完毕！消耗时间：" + (end - beg) + "ms");
        System.out.println("t1:" + t1 + "t2:" + t2);
    }

    private void parseHTML(File f) {
        // 1.解析出 HTML 的文件标题
        String title = parseTitle(f);
        // 2.解析出 HTML 对应的 URL
        String url = parseUrl(f);
        // 3.解析出 HTML 的文件正文
        long beg = System.nanoTime();
        String content = parseContent(f);
        long mid = System.nanoTime();
        // 4、 把解析出来的信息加入到索引 index 中
        index.addDoc(title, url, content);
        long end = System.nanoTime();

        // 这里还不能用 print, 因为频繁打印反而有可能拖慢速度
        t1.addAndGet(mid - beg);
        t2.addAndGet(end - mid);
    }

    private String parseTitle(File f) {
        String name = f.getName();
//      return name.substring(0, name.lastIndexOf("."));
        return name.substring(0, name.length() - ".html".length());
    }

    private String parseUrl(File f) {
        String part1 = "https://docs.oracle.com/javase/8/docs/api/";
        String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
        return part1 + part2;
    }

    public String parseContent(File f) {
        // 先一个字符一个字符读取，以 < 和 > 来判断是否开始拷贝数据
        // 手动把缓冲区设置成 1M 大小
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f), 1024 * 1024)){
            // 判断是否开始拷贝
            boolean isCopy = true;
            StringBuilder content = new StringBuilder();
            while (true) {
                // ret 返回值为 int, 而不是 char,
                // 主要是为了表示一些非法情况,
                // 比如读到了文件末尾，再继续读，就返回 -1
                int ret = bufferedReader.read();
                if (ret == -1){
                    // 表示文件读完了
                    break;
                }
                // 若结果不为 -1, 代表是一个合法字符
                char c = (char)ret;
                if(isCopy){
                    // 拷贝条件合法的情况下，遇到普通自负就拷贝到 StringBuilder 中
                    if(c == '<'){
                        isCopy = false;
                        continue;
                    }
                    if (c == '\n' || c == '\r'){
                        // 把换行替换成空格
                        c = ' ';
                    }
                    content.append(c);
                }else {
                    if (c == '>'){
                        isCopy = true;
                    }
                }
            }
            return content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    private String readFile(File f) {
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f))) {
            StringBuilder content = new StringBuilder();
            while (true) {
                int ret = bufferedReader.read();
                if (ret == -1) {
                    break;
                }
                char c = (char) ret;
                if(c == '\n' || c == '\r') {
                    c = ' ';
                }
                content.append(c);
             }
            return content.toString();
        } catch (IOException e){
            e.printStackTrace();
        }
        return "";
    }

    // 这个方法内部基于正则表达式，实现去标签，以及去除 script 标签
    public String parseContentByRegex(File f) {
        // 1、 先把整个文件都读到 String 中
        String content = readFile(f);
        // 2、 替换掉 script 标签
        content = content.replaceAll("<script.*>(.*?)</script>", " ");
        // 3、 替换掉普通的 html 标签
        content = content.replaceAll("<.*?>", " ");
        // 4、 适用正则表达式，把多个空格，合并成一个空格
        content = content.replaceAll("\\s+", " ");
        return content;
    }

    // 参数： 递归遍历起始目录、 递归得到的结果
    private void enumFile(String inputPath, ArrayList<File> fileList){
        File rootPath = new File(inputPath);

        // listFiles 能够获取到 rootPath 当前目录下所包含的文件/目录
        // 使用 listFiles 只能看到一级目录，而看不了子目录里的内容
        // 所以还需要借助递归，以实现查看子目录内容这个功能
        File[] files = rootPath.listFiles();
        for (File f: files) {
            // 根据 f 的类型，来判断是否要加入递归
            // f 为普通文件： 直接加入 fileList 结果中；
            // f 为目录： 递归调用 enumFile 这个方法，以进一步获取子目录内容
            if(f.isDirectory()){
                enumFile(f.getAbsolutePath(), fileList);
            }else{
                if(f.getAbsolutePath().endsWith(".html")){
                    fileList.add(f);
                }
            }
        }
    }

    public static void main(String[] args) {
        Parser parser = new Parser();
        parser.run();
    }
}
